Перейти к основному содержимому

5.10. Основные принципы обработки ошибок в Go

Разработчику Архитектору

Основные принципы обработки ошибок в Go

Основные принципы обработки ошибок в Go

  1. Ошибки — это значения.
    Тип error — это встроенный интерфейс:

    type error interface {
    Error() string
    }
  2. Функции возвращают ошибку как последнее значение (по соглашению):

    func doSomething() (Result, error)
  3. Нет try/catch, throw, raise.
    Ошибки проверяются явно с помощью условного оператора:

    if err != nil {
    // обработка ошибки
    }
  4. Паника (panic) существует, но не предназначена для обычной обработки ошибок.

    • panic(v interface{}) прерывает нормальное выполнение.
    • recover() может быть вызван в отложенной функции (defer) для перехвата паники.
    • Используется только для некорректируемых ошибок (например, нарушение инвариантов, ошибки инициализации).

Встроенные типы ошибок

Go не предоставляет иерархии стандартных классов ошибок. Однако в стандартной библиотеке определены некоторые конкретные типы, реализующие интерфейс error:

1. errors.errorString (неэкспортируемый)

  • Внутренний тип, используемый функцией errors.New("message").
  • Содержит только строковое сообщение.

2. Общие ошибки из стандартной библиотеки:

  • io.EOF — сигнал конца потока (не ошибка в обычном смысле).
  • io.ErrUnexpectedEOF
  • io.ErrNoProgress
  • os.ErrInvalid
  • os.ErrPermission
  • os.ErrExist
  • os.ErrNotExist
  • syscall.Errno — системные ошибки (например, ENOENT, EACCES), которые также реализуют error.

3. Пакет errors (начиная с Go 1.13) предоставляет поддержку:

  • Оборачивания ошибок: fmt.Errorf("...: %w", err)
  • Проверки цепочки ошибок: errors.Is(err, target), errors.As(err, &target)

Это позволяет строить цепочки ошибок с контекстом, но без иерархии типов.


Паники (аварийные остановки)

Хотя паники не являются «ошибками» в обычном смысле, они могут быть вызваны встроенным кодом:

  • Нарушение границ массива: index out of range
  • Разыменование нулевого указателя: invalid memory address or nil pointer dereference
  • Вызов метода у nil-интерфейса
  • Конкурентное изменение map без мьютекса: concurrent map writes
  • Переполнение стека: stack overflow
  • Ошибки при разыменовании интерфейса: interface conversion: ...

Эти ситуации вызывают panic, но не представляют собой именованные типы исключений — сообщение передаётся как строка или встроенная константа.


Нет типов ошибок

В Go нет списка «типов ошибок», аналогичного другим языкам, потому что:

  • Ошибки — это значения произвольных типов, реализующих интерфейс error.
  • Нет иерархии наследования.
  • Нет встроенных классов вроде IndexError, KeyError и т.п.
  • Стандартная библиотека использует семантические переменные-ошибки (os.ErrNotExist) или динамические сообщения.

Таким образом, вместо таксономии исключений в Go применяется дисциплина явной проверки возвращаемых значений и использование контекстуальных сообщений.

Если требуется классификация ошибок, разработчик сам определяет соответствующие типы:

type ValidationError struct {
Field string
Msg string
}

func (e ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Msg)
}

Такой подход обеспечивает гибкость, но отказывается от глобальной иерархии ошибок.